package parsing.productions;

import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;

import world.Critter;

public class Factor extends MutableNode implements NumberNode{
	// Invariant for children: children has at least one Atom.
	// children alternates Atom and MulOp, starting with Atom and having an odd number of elements.
	
	/**
	 * Constructor: creates an instance of Factor whose children are all the Nodes in allChildren.
	 * @param allChildren list of children.
	 * Requires: Contents of allChildren are in form: Atom ( MulOp Atom )*
	 */
	public Factor(List<AbstractNode> allChildren) {
		if(allChildren.size() > 0)
			children= allChildren;
		else
			throw new IllegalArgumentException("There must be at least one child to make this production.");
	}

	@Override
	public void prettyPrint(StringBuffer sb) {
		for(Node n : children)
			n.prettyPrint(sb);
	}

	@Override
	public void mutate(Program genome) {
//		StringBuffer sb = new StringBuffer();
//		prettyPrint(sb);
//		sb.append("was changed to ");
		Random rnd = new Random();
		int mutationType;
		if (children.size() == 1) {					  //since the randomly choose/generate option is being eliminated,
			((Atom) children.get(0)).mutate(genome);  //the only option here is to mutate the child.
			return;
		} else {
			mutationType = rnd.nextInt(2) + 1;
		}
		if (mutationType == 0) { // Mutates by replacing an atom with a randomly generated Atom.
			int randomIndex = rnd.nextInt(children.size()/2 + 1);
			int evenIndex = randomIndex*2; // This chooses a random even index so that an Atom is chosen.
			Atom newAtom = AbstractNode.chooseAtom(genome, children.get(evenIndex));
			children.set(evenIndex, newAtom);
		} else if (mutationType == 1) { // Mutates by reversing order of children.
			Collections.reverse(children);
		} else { // Mutates by replacing this Factor's children with one of its children.
			int randomIndex = rnd.nextInt(children.size()/2 + 1);
			int evenIndex = randomIndex*2; // This chooses a random even index so that an Atom is chosen.
			AbstractNode replace = children.get(evenIndex);
			children.clear();
			children.add(replace);
		}
//		prettyPrint(sb);
//		System.out.println(sb);
	}

	@Override
	public Node duplicate() {
		List<AbstractNode> duplicateChildren= new LinkedList<AbstractNode>();
		for (AbstractNode n : children) {
			duplicateChildren.add((AbstractNode)n.duplicate());
		}
		return new Factor(duplicateChildren);
	}

	@Override
	public double getValue(Critter c) {
		double value = ((Atom) children.get(0)).getValue(c);
		for(int child = 1; child < children.size(); child += 2){
			int opType = ((MulOp) children.get(child)).getType();
			if(opType == MulOp.TIMES)
				value *= ((Atom) children.get(child + 1)).getValue(c);
			else if(opType == MulOp.DIVIDE){
				double childValue = ((Atom) children.get(child + 1)).getValue(c);
				if(childValue == 0)
					childValue = 1;
				value /= childValue;
			}
			else //MOD
				value %= ((Atom) children.get(child + 1)).getValue(c);
		}
		return value;
	}
}
